import { isArray, isObject, isString, isFunction, startsWith, reduce, merge, map, each } from "lodash";
import resizeObserver from "@/services/resizeObserver";
// import { Plotly, prepareData, prepareLayout, updateData, updateAxes, updateChartSize } from "../plotly";
import { prepareData, prepareLayout, updateData, updateAxes, updateChartSize } from "../plotly";

function createErrorHandler(errorHandler) {
  return error => {
    // This error happens only when chart width is 20px and looks that
    // it's safe to just ignore it: 1px less or more and chart will get fixed.
    if (isString(error) && startsWith(error, "ax.dtick error")) {
      return;
    }
    errorHandler(error);
  };
}

// This utility is intended to reduce amount of plot updates when multiple Plotly.relayout
// calls needed in order to compute/update the plot.
// `.append()` method takes an array of two element: first one is a object with updates for layout,
// and second is an optional function that will be called when plot is updated. That function may
// return an array with same structure if further updates needed.
// `.process()` merges all updates into a single object and calls `Plotly.relayout()`. After that
// it calls all callbacks, collects their return values and does another loop if needed.
function initPlotUpdater(Plotly) {
  let actions = [];

  const updater = {
    append(action) {
      if (isArray(action) && isObject(action[0])) {
        actions.push(action);
      }
      return updater;
    },
    process(plotlyElement) {
      if (actions.length > 0) {
        const updates = reduce(actions, (updates, action) => merge(updates, action[0]), {});
        const handlers = map(actions, action => (isFunction(action[1]) ? action[1] : () => null));
        actions = [];
        return Plotly.relayout(plotlyElement, updates).then(() => {
          each(handlers, handler => updater.append(handler()));
          return updater.process(plotlyElement);
        });
      } else {
        return Promise.resolve();
      }
    },
  };

  return updater;
}

function getXColumn(options) {
  var result;
  each(options.columnMapping, (type, column) => {
    if (type === "x") {
      result = column;
      return false;
    }
  });          
  return result;
}

function getClickColumn(options) {
  var result;
    
  if (options.hasOwnProperty("clickColumn")) {
    result = options.clickColumn;
  } else {
    each(options.columnMapping, (type, column) => {
      if (type === "clickColumn") {
        result = column;
        return false;
      }
    });     
  }

  return result;
}

export default function initChart(Plotly, container, options, data, additionalOptions, onClickHandler, onError, showPortal = false) {
  const handleError = createErrorHandler(onError);

  const plotlyOptions = {
    showLink: false,
    displaylogo: false,
  };

  if (additionalOptions.hidePlotlyModeBar) {
    plotlyOptions.displayModeBar = false;
  }
  //高级配置的config的属性
  if (options.hasOwnProperty("config") && isObject(options.config)) {
    merge(plotlyOptions, options.config);
  }

  const plotlyData = prepareData(data, options, showPortal);
  var plotlyLayout = prepareLayout(container, options, plotlyData, showPortal);
  //高级配置的layout的属性
  if (options.hasOwnProperty("layout") && isObject(options.layout)) {
    merge(plotlyLayout, options.layout);
  }

  let isDestroyed = false;

  let updater = initPlotUpdater(Plotly);

  function createSafeFunction(fn) {
    return (...args) => {
      if (!isDestroyed) {
        try {
          return fn(...args);
        } catch (error) {
          handleError(error);
        }
      }
    };
  }

  let unwatchResize = () => {};

  const promise = Promise.resolve()
    .then(() => Plotly.newPlot(container, plotlyData, plotlyLayout, plotlyOptions))
    .then(
      createSafeFunction(() =>
        updater
          .append(updateAxes(container, plotlyData, plotlyLayout, options))
          .append(updateChartSize(container, plotlyLayout, options))
          .process(container)
      )
    )
    .then(
      createSafeFunction(() => {
        options.clickEnabled && !!onClickHandler && container.on('plotly_click', 
          createSafeFunction(currentData => {
            var key = getXColumn(options);
            var value =  options.globalSeriesType === "pie" ? currentData.points[0].label : (options.swappedAxes ? currentData.points[0].y : currentData.points[0].x);

            let clickColumn = getClickColumn(options);
            if (!!clickColumn) {
              for (var i=0; i<data.length; i++) {
                if (!!data[i].data) {
                  each(data[i].data, row => {
                    var is_equal = false;
                    if (row.$raw[key] === value)  is_equal = true;
                    if (!is_equal) {
                      if (row.$raw[key].toString() === value.toString()) is_equal = true;
                    }
                    if (!is_equal) {
                      if (row.$raw[key].hasOwnProperty('_isAMomentObject') && row.$raw[key]._isAMomentObject) {
                        if (row.$raw[key].format('YYYY-MM-DD HH:mm:ss') === value.toString()) 
                          is_equal = true;
                        else if (new Date(row.$raw[key].format()).getTime() == new Date(value.toString()).getTime()) 
                          is_equal = true;
                      }
                    }
                    if (is_equal) {
                      key = clickColumn;
                      value = row.$raw[key].toString();
                      return false;
                    }
                  })
                }
              }
            }

            if (!!options.clickParameter) key = options.clickParameter;

            let obj = {};
            obj[key] = value;
            onClickHandler(obj);
          })
        );
  
        container.on(
          "plotly_restyle",
          createSafeFunction(updates => {
            // This event is triggered if some plotly data/layout has changed.
            // We need to catch only changes of traces visibility to update stacking
            if (isArray(updates) && isObject(updates[0]) && updates[0].visible) {
              updateData(plotlyData, options);
              updater.append(updateAxes(container, plotlyData, plotlyLayout, options)).process(container);
            }
          })
        );
        options.onHover && container.on("plotly_hover", options.onHover);
        options.onUnHover && container.on("plotly_unhover", options.onUnHover);

        unwatchResize = resizeObserver(
          container,
          createSafeFunction(() => {
            updater.append(updateChartSize(container, plotlyLayout, options)).process(container);
          })
        );
      })
    )
    .catch(handleError);
  
  const result = {
    initialized: promise.then(() => result),
    setZoomEnabled: createSafeFunction(allowZoom => {
      const layoutUpdates = { dragmode: allowZoom ? "zoom" : false };
      return Plotly.relayout(container, layoutUpdates);
    }),
    destroy: createSafeFunction(() => {
      isDestroyed = true;
      if (container && typeof container.removeAllListeners === "function") container.removeAllListeners("plotly_restyle");
      unwatchResize();
      delete container.__previousSize; // added by `updateChartSize`
      Plotly.purge(container);
    }),
  };

  return result;
}
